package com.panorama.visitor;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import com.panorama.visitor.annotation.Visitable;

public abstract class AbstractVisitor<R> implements Visitor<R> {
	public abstract Object get(Object node, Field field);
	
	@Override
	public void visit(VisitableNode node) {
	}
	
	public void visitChildren(VisitableNode node) {
		List<Field> visitableFields = visitableFields(node);
		
		for(Field field: visitableFields) {
			visitField(node, field);
		}
	}
	
	private List<Field> visitableFields(Object node) {
		List<Field> fields = new LinkedList<Field>();
		
		for(Field field: node.getClass().getDeclaredFields()) {
			if(field.isAnnotationPresent(Visitable.class)) {
				fields.add(field);
			}
		}
		
		return fields;
	}
	

	@SuppressWarnings("unchecked")
	protected void visitField(VisitableNode node, Field field) {
		//System.out.println("VISIT FIELD: " + field);
		Class<?> fieldClazz = field.getType();
		
		//System.out.println("FIELD'S CLASS: " + fieldClazz);
		
		// this field is a collection of children
		if(Collection.class.isAssignableFrom(fieldClazz)) {
			ParameterizedType genericType = (ParameterizedType) field.getGenericType();
			if(VisitableNode.class.isAssignableFrom((Class<?>) genericType.getActualTypeArguments()[0])) {
				Collection<? extends VisitableNode> childCollection = null;
				
				childCollection = (Collection<? extends VisitableNode>) get(node, field);
				
				if(childCollection != null) {
					visitCollection(node, childCollection);
				}
			} else {
				// TODO
				throw new Error("Collection does not contain visitable nodes.");
			}
		}
		// this field is a single child
		else if(VisitableNode.class.isAssignableFrom(fieldClazz)) {
			//System.out.println("SINGLE FIELD");
			VisitableNode childNode = null;
			
			childNode = (VisitableNode) get(node, field);
			
			if(childNode != null) {
				visitSingleChild(node, childNode);
			}
		} else {
			// TODO
			throw new Error("INVALID VISITABLE FIELD: " + field);
		}
	}

	private void visitCollection(VisitableNode node, Collection<? extends VisitableNode> childCollection) {
		List<R> childrenReturnValues = new LinkedList<R>();
		
		for(VisitableNode child: childCollection) {
			childrenReturnValues.add(child.accept(this));
		}
		
		collect(childrenReturnValues);
	}

	private void visitSingleChild(VisitableNode node, VisitableNode childNode) {
		R childReturnValue = childNode.accept(this);
		
		collect(childReturnValue);
	}
	
	public Visitor<R> enter(VisitableNode node) {
		return this;
	}
	
	public R leave(VisitableNode node) {
		return null;
	}
	
	public R overrideValue(VisitableNode node) {
		return null;
	}
	
	public boolean override(VisitableNode node) {
		return false;
	}
	
	public void collect(List<R> values) {
	}
	
	public void collect(R value) {
		
	}

}
